pragma License (GPL);
--
-- drk-bigrat.adb
--
-- Copyright (C) 2012 - Thibault BUREL
--
-- This file is part of DRK.BigRat.
--
-- DRK.BigRat is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- DRK.BigRat is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with DRK.BigRat.  If not, see <http://www.gnu.org/licenses/>
--

package body DRK.BigRat is

   use type BigInt.Instance;

   --------------------------------------
   --  Private functions declarations  --
   --------------------------------------

   function Internal_Addition (Left, Right : in Instance) return Instance;
   --  This function returns ad+bc / db without taking care of the sign of the
   --  parameters.

   function Is_Null (R : in Instance) return Boolean;
   --  Due to an implementation choice, BigRat can represent null in two way :
   --  => - 0 / den or
   --  => + 0 / den.
   --  This function deals with this problem.

   function Product_Sign (Left, Right : in Instance) return Boolean;
   pragma Inline(Product_Sign);

   -----------------------------
   --  Functions definitions  --
   -----------------------------

   function "="  (Left, Right : in Instance) return Boolean
   is
      a, b : BigInt.Instance;
   begin

      if Is_Null (Left) and Is_Null (Right) then
         --  This branch is tested by Equals_01

         return True;

      else

         if Left.Pos = Right.Pos then
            --  This branch is tested by Equals_02

            --
            --   a     c
            --  --- = ---  <=> ad = cb
            --   b     d
            --
            a := Left.Num * Right.Den;
            b := Left.Den * Right.Num;

            return a = b;

         else
            --  This branch is tested by Equals_03

            return False;

         end if;

      end if;

   end "=";

   function "<"  (Left, Right : in Instance) return Boolean
   is
      a, b : BigInt.Instance;
   begin

      if Is_Null (Left) or Is_Null (Right) then

         if Is_Null (Left) and Is_Null (Right) then
            --  This branch is tested by Less_01

            return False;

         else

            if Is_Null (Left) then

               if Right.Pos = True then
                  --  This branch is tested by Less_02

                  return True;

               else
                  --  This branch is tested by Less_03

                  return False;

               end if;

            else

               if Left.Pos = True then
                  --  This branch is tested by Less_04

                  return False;

               else
                  --  This branch is tested by Less_05

                  return True;

               end if;

            end if;

         end if;

      else

         if Left.Pos = Right.Pos then

            a := Left.Num * Right.Den;
            b := Left.Den * Right.Num;

            if Left.Pos = True then
               --  This branch is tested by Less_06

               return a < b;

            else
               --  This branch is tested by Less_07

               return a > b;

            end if;

         else

            if Left.Pos = False then
               --  This branch is tested by Less_08

               return True;

            else
               --  This branch is tested by Less_09

               return False;

            end if;

         end if;

      end if;

   end "<";

   function ">"  (Left, Right : in Instance) return Boolean
   is
      a, b : BigInt.Instance;
   begin

      if Is_Null (Left) or Is_Null (Right) then

         if Is_Null (Left) and Is_Null (Right) then

            return False;

         else

            if Is_Null (Left) then

               if Right.Pos = True then

                  return False;

               else

                  return True;

               end if;

            else

               if Left.Pos = True then

                  return True;

               else

                  return False;

               end if;

            end if;

         end if;

      else

         if Left.Pos = Right.Pos then

            a := Left.Num * Right.Den;
            b := Left.Den * Right.Num;

            if Left.Pos = True then

               return a > b;

            else

               return a < b;

            end if;

         else

            if Left.Pos = False then

               return False;

            else

               return True;

            end if;

         end if;

      end if;

   end ">";

   function "<=" (Left, Right : in Instance) return Boolean
   is
   begin
      return not (Left > Right);
   end "<=";

   function ">=" (Left, Right : in Instance) return Boolean
   is
   begin
      return not (Left < Right);
   end ">=";

   function "+" (Left, Right : in Instance) return Instance
   is
      Result : Instance;
      Tmp    : Instance;
   begin

      if Left.Pos = Right.Pos then
         --  This branch is tested by Add_01

         Result     := Internal_Addition (Left, Right);
         Result.Pos := Left.Pos;

      else

         if Left.Pos = True then
            --  This branch is tested by Add_02

            Tmp     := Right;
            Tmp.Pos := True;

            Result := Left - Tmp;

         else
            --  This branch is tested by Add_03

            Tmp     := Left;
            Tmp.Pos := True;

            Result := Right - Tmp;

         end if;

      end if;

      return Result;

   end "+";

   function "-" (Left, Right : in Instance) return Instance
   is
      Result : Instance;
   begin

      if Left.Pos /= Right.Pos then
         --  This branch is tested by Minus_01

         Result     := Internal_Addition (Left, Right);
         Result.Pos := Left.Pos;

      else

         declare
            Tmp1 : constant BigInt.Instance := Left.Num * Right.Den;
            Tmp2 : constant BigInt.Instance := Left.Den * Right.Num;
         begin

            if Tmp1 > Tmp2 then
               --  This branch is tested by Minus_02

               Result.Num := Tmp1 - Tmp2;
               Result.Pos := Left.Pos;

            else
               --  This branch is testec by Minus_03

               Result.Num := Tmp2 - Tmp1;
               Result.Pos := not Left.Pos;

            end if;

         end;

         Result.Den := Left.Den * Right.Den;

      end if;

      return Result;

   end "-";

   function "*" (Left, Right : in Instance) return Instance
   is
      Result : Instance;
   begin
      --  Tested by Mul_01 & Mul_02

      Result.Pos := Product_Sign (Left, Right);

      Result.Num := Left.Num * Right.Num;
      Result.Den := Left.Den * Right.Den;

      return Result;

   end "*";

   function "/" (Left, Right : in Instance) return Instance
   is
      Result : Instance;
   begin
      --  Tested by Div_01

      Result.Pos := Product_Sign (Left, Right);

      Result.Num := Left.Num * Right.Den;
      Result.Den := Left.Den * Right.Num;

      return Result;

   end "/";

   function Init (Num, Den : in Natural) return Instance
   is
      New_Rat           : Instance;
   begin

      New_Rat.Pos := True;
      New_Rat.Num := BigInt.Init (Num);
      New_Rat.Den := BigInt.Init (Den);

      return New_Rat;

   end Init;

   function Init_Pow_10 (Exp : in Integer) return Instance
   is
      Result : Instance;
   begin

      Result.Pos := True;

      if Exp >= 0 then

         Result.Num := BigInt.Init_Pow_10 (Exp);
         Result.Den := BigInt.Init (1);

      else

         Result.Num := BigInt.Init (1);
         Result.Den := BigInt.Init_Pow_10 (-Exp);

      end if;

      return Result;

   end Init_Pow_10;

   function Internal_Addition (Left, Right : in Instance) return Instance
   is
      Result : Instance;
   begin

      Result.Pos := True;
      Result.Num := Left.Num * Right.Den + Left.Den * Right.Num;
      Result.Den := Left.Den * Right.Den;

      return Result;

   end Internal_Addition;

   function Is_Null (R : in Instance) return Boolean
   is
   begin

      return R.Num = BigInt.Init(0);

   end Is_Null;

   function Product_Sign (Left, Right : in Instance) return Boolean
   is
   begin

      if Left.Pos = Right.Pos then
         --  This branch is tested by Mul_01

         return True;

      else
         --  This branch is tested by Mul_02

         return False;

      end if;

   end Product_Sign;

   procedure Set_Precision (P : in Instance)
   is
   begin
      Epsilon := P;
   end Set_Precision;

begin

   Epsilon := Init_Pow_10 (Default_Precision);

end DRK.BigRat;
